#ifndef _INTERPRETER_
#define _INTERPRETER_

#include <string>
#include <vector>

#include "FundamentalTypes.h"
#include "common/Instruction.h"

#define DEBUG_GC_TIME 0

#if DEBUG_GC_TIME==1
    #include <ctime>
#endif

class Interpreter
{
    friend class Loader;
public:

    Interpreter(const std::string& filename);
    ~Interpreter();

    int Run(int argc, char ** argv);

private:

    // Instructions
    void Add();
    void Subtract();
    void Multliply();
    void Divide();
    void Modulus();
    void And();
    void Or();
    void Equal();
    void NotEqual();
    void LessThan();
    void GreaterThan();
    void LessOrEqual();
    void GreaterOrEqual();
    void Negation();
    void UnaryMinus();
    void GetLocal(int i);
    void SetLocal(int i);
    void GetArrayValue();
    void SetArrayValue();
    void GetFieldValue(int i);
    void SetFieldValue(int i);
    void IntConst(int val);
    void StringConst(int i);
    void FunctionCall(int addr, int params);
    void BuiltInCall(int i, int params);
    void MethodCall(int i, int params);
    void New(int i, int params);
    void Frame(int count);
    void Return();
    void ReturnValue();
    void JumpIfTrue(int offset);
    void JumpIfFalse(int offset);
    void Jump(int offset);

    // Util
    void PrepareMainArguments(int argc, char ** argv);
    typedef void (Interpreter::*BuiltInFunction)(int);
    typedef std::unordered_map<int, BuiltInFunction> BuiltInFunctions;
    void AddBuiltInFunction(const std::string& name, BuiltInFunction f);
    void BindBuiltIns();
    void PushRetAddr();
    const std::string& AddrToName(Bytecode addr);
    void PrintStackTrace();
    bool ShouldPerformGC();
    void GC();
    void CleanRefTable();
    StackElem * Allocate(IntType count);
    ObjectTableElem ** AllocateObject(const Class& c);
    ObjectTableElem ** AllocateArray(IntType elems);
    ObjectTableElem ** AllocateString(IntType len);
    ObjectTableElem ** AllocateArrayOfStrings(const std::vector<std::string>& strings);
    void ReallocateArray(ObjectTableElem& array);

    // Built-in functions
    void Print(int params);
    void PrintLn(int params);
    void Array(int params);
    void ArraySize(int params);
    void ArrayPush(int params);
    void Exit(int params);
    void ParseInt(int params);
    void ReadLines(int params);
    void SplitString(int params);

    template <typename T>
    inline T Get(const char * const ptr)
    {
        return *reinterpret_cast<const T * const>(ptr);
    }

    template <typename T>
    inline T GetInc(const char *& ptr)
    {
        return *reinterpret_cast<const T *&>(ptr)++;
    }

    enum { DefaultStackSize = 512 * 1024 };
    enum {  DefaultHeapSize = 512 * 1024 };

    static const int DefaultHeapBytes = 128 * 1024 * 1024; // 128 MB
    static const int DefaultHeapElems = DefaultHeapBytes / sizeof(StackElem);

    static const int DefaultRefTableSize = 512 * 1024;

    // mozne parametry GC
    static const int HeapAllocationsBeforeGC = 1024;
    static const int InstructionsBeforeGC = 1024;

    StackElem * const stack;
    ObjectTableElem * src_ref_table, * dst_ref_table;
    char * bytecode;
    StringPool pool;
    AddrToNameMap addr_to_name;
    BuiltInFunctions built_ins;
    UndefinedFunctions undefs;
    Classes classes;
    ObjectTable obj_table;

    static const char fake_main_callsite[2];
    static Bytecode const exit_pc;

    Bytecode pc;    // program counter
    StackElem * sp, * fp;   // stack pointer, frame pointer
    ObjectTableElem * ref_table_ptr;  // heap pointer

    // ukazatel tesne za alokovanou haldu - pokud se pokusim alokovat neco sem, nedovolim to
    ObjectTableElem * obj_table_end;
    StackElem * stack_end;

    int entry_point;

    int allocs_since_gc;
    int insns_since_gc;
    
    StackElem * src_heap, * dst_heap;
    StackElem * alloc_ptr;

    #if DEBUG_GC_TIME==1
        int fTotalGCRuns;
        clock_t fDeletePart1Clock;
        clock_t fDeletePart2Clock;
        clock_t fRootSetSearchClock;
        clock_t fSpaghettiPullingClock;
        clock_t fRefTableCleanClock;
    #endif
};

#endif
